package com.zhm.demo.redis.config;

import com.github.benmanes.caffeine.cache.Caffeine;
import com.zhm.demo.redis.RedisTemplateUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.cache.support.AbstractValueAdaptingCache;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.lang.Nullable;

import java.time.Duration;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

/**
 * 一句话功能简述;
 *
 * @Author 姓名_工号（例如pengmin_M008402）
 * @Version v1.0
 * @Date:2023-06-03 13:49
 */
@Slf4j
@EnableCaching
@Configuration
public class MyCacheConfig {

    public static final String CACHE_TEST_KEY = "cache:spring:mix";

    private LettuceConnectionFactory lettuceConnectionFactory = RedisTemplateUtil.makeSitConnect(0);

    public static class CaffeineRedisMixCache extends AbstractValueAdaptingCache {

        private String name;

        private CaffeineCacheManager caffeineCacheManager;

        private RedisCacheManager redisCacheManager;

        public CaffeineRedisMixCache(String name, boolean allowNullValues, CaffeineCacheManager caffeineCacheManager, RedisCacheManager redisCacheManager) {
            super(allowNullValues);
            this.name = name;
            this.caffeineCacheManager = caffeineCacheManager;
            this.redisCacheManager = redisCacheManager;
        }

        @Override
        protected Object lookup(Object key) {
            ValueWrapper valueWrapper = caffeineCacheManager.getCache(name).get(key);
            if (null == valueWrapper || null == valueWrapper.get()) {
                valueWrapper = redisCacheManager.getCache(name).get(key);
            }
            if (null != valueWrapper && null != valueWrapper.get()) {
                caffeineCacheManager.getCache(name).put(key, valueWrapper.get());
            }
            return null == valueWrapper ? null : valueWrapper.get();
        }

        @Override
        public String getName() {
            return name;
        }

        @Override
        public Object getNativeCache() {
            return null;
        }

        @Override
        public <T> T get(Object key, Callable<T> valueLoader) {
            T t = caffeineCacheManager.getCache(name).get(key, valueLoader);
            if (null != t) {
                return t;
            }
            return redisCacheManager.getCache(name).get(key, valueLoader);
        }

        @Override
        public void put(Object key, Object value) {
            caffeineCacheManager.getCache(name).put(key, value);
            redisCacheManager.getCache(name).put(key, value);
        }

        @Override
        public void evict(Object key) {
            caffeineCacheManager.getCache(name).evict(key);
            redisCacheManager.getCache(name).evict(key);
        }

        @Override
        public void clear() {
            caffeineCacheManager.getCache(name).clear();
            redisCacheManager.getCache(name).clear();
        }
    }

    public static class CaffeineRedisMixCacheManager implements CacheManager {

        private static Map<String, CaffeineRedisMixCache> caffeineRedisMixCacheMap = new ConcurrentHashMap<>();

        private CaffeineCacheManager caffeineCacheManager;

        private RedisCacheManager redisCacheManager;

        public CaffeineRedisMixCacheManager(CaffeineCacheManager caffeineCacheManager, RedisCacheManager redisCacheManager) {
            this.caffeineCacheManager = caffeineCacheManager;
            this.redisCacheManager = redisCacheManager;
        }


        @Override
        @Nullable
        public Cache getCache(String name) {
            CaffeineRedisMixCache cache = caffeineRedisMixCacheMap.get(name);
            if (null != cache) {
                return cache;
            }
            cache = new CaffeineRedisMixCache(name, true, caffeineCacheManager, redisCacheManager);
            caffeineRedisMixCacheMap.put(name, cache);
            return cache;
        }

        @Override
        public Collection<String> getCacheNames() {
            return caffeineRedisMixCacheMap.keySet();
        }
    }

    @Primary
    @Bean
    public CaffeineRedisMixCacheManager mixCacheManager(CaffeineCacheManager caffeineCacheManager, RedisCacheManager redisCacheManager) {
        final CaffeineRedisMixCacheManager caffeineRedisMixCacheManager = new CaffeineRedisMixCacheManager(caffeineCacheManager, redisCacheManager);
        return caffeineRedisMixCacheManager;
    }


    @Bean
    public CaffeineCacheManager caffeineCacheManager() {
        Caffeine caffeine = Caffeine.newBuilder()
                .initialCapacity(100)
                //缓存的最大条数
                .maximumSize(1000)
                .removalListener((k, v, rs) -> {
                    log.debug("{} {} {}", k, v, rs);
                })
//                .expireAfter(new Expiry<Object, Object>() {
//                    @Override
//                    public long expireAfterCreate(@NonNull Object key, @NonNull Object value, long currentTime) {
//                        return 0;
//                    }
//
//                    @Override
//                    public long expireAfterUpdate(@NonNull Object key, @NonNull Object value, long currentTime, @NonNegative long currentDuration) {
//                        return 0;
//                    }
//
//                    @Override
//                    public long expireAfterRead(@NonNull Object key, @NonNull Object value, long currentTime, @NonNegative long currentDuration) {
//                        return 0;
//                    }
//                })
//                .refreshAfterWrite(5,TimeUnit.SECONDS)
                .expireAfterAccess(60, TimeUnit.SECONDS);

        CaffeineCacheManager cacheManager = new CaffeineCacheManager();
        cacheManager.setAllowNullValues(true);
        cacheManager.setCaffeine(caffeine);
//        cacheManager.setCacheLoader(cacheLoader);
//        cacheManager.setCacheNames(Arrays.asList("caffeine"));
        return cacheManager;
    }


    @Bean
    public RedisCacheConfiguration redisCacheConfiguration() {
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration
                .defaultCacheConfig()
                .entryTtl(Duration.ofHours(1))
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(StringRedisSerializer.UTF_8))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(RedisTemplateUtil.stringRedisSerializer));
        return redisCacheConfiguration;
    }

    @Bean
    public RedisCacheManager redisCacheManager(RedisCacheConfiguration redisCacheConfiguration) {

        Map<String, RedisCacheConfiguration> configurationMap = new HashMap<>(16);
        configurationMap.put(CACHE_TEST_KEY, redisCacheConfiguration.entryTtl(Duration.ofSeconds(60)).disableCachingNullValues());

        RedisCacheManager cacheManager = RedisCacheManager
                .builder(RedisCacheWriter.lockingRedisCacheWriter(lettuceConnectionFactory))
//                .initialCacheNames(Sets.newHashSet(CACHE_TEST_KEY))
                .cacheDefaults(redisCacheConfiguration)
                .withInitialCacheConfigurations(configurationMap)
                .transactionAware()
                .build();
        return cacheManager;
    }

}
